A magic game based on Raspberry Pi
A Project By Huan Yang(hy468) and Hao Chen(hc795)
Magic Potter is a player VS player game based on Raspberry Pi, each player will have a magic wand and wave it in front of the camera. The system will recognize the trajectory of the wand and tansform into the corresponding spell. There were a total of three rounds in which each player waved his wand in front of the camera in turn. To show the game more clearly, the battle process and the winner will display on the Pi TFT. And for the relationship between different spells, it needs players to explore. For each round, the stronger spell wins.
At first, we planned to make a water gun, when the system recognize the corresponding trajectory, it will shoot out water to the user. However, after consideration, we afraid that the water may lead the system to short circuit, which is dangerous. And the first edition of freehand sketch is shown below. At last, we decided to make an adversarial game.
Freehand sketch of the water gun
As for the whole system, it mainly has four parts and they are Raspberry Pi, the Raspberry camera, the Pi TFT and the LED part. The raspberry is responsable for processing the whonle pragram and issue instructions to various modules of the system. the Raspberry camera is to capture the video stream to the Raspberry pi. In addition, the Led part is to show the magic spell in a more clear way. Pi TFT is used to show the game process.
And for the running process, at first the camera will capture video stream and pass it to Raspberry Pi. Then the Pi change the frames to gray scales and then we call a function called "cv2.HoughCircles" to detect circle (the reason is that the front end of wand is circle). And next it will compare the coordinates of circle in current frame with that in last frame. Finally the result of this comparison will give us an idea of the direction in which the wand is moving and print the corresponding spells. In addition the pygame module will show the game process on PiTFF.
Overall schematic circuit diagram of Magic Potter
As for the overall structure, we use a box to cover the system parts, and the final demo is shown below.
Overall structure of Magic Potter
Detailed assembling parts
Pi Camera
PiTFT
LEDs
After we finished the first edition demo. we have several steps to test the functions of the system. First of all, we used different speed to wave the magic wand to test the the recognition accuracy of the camera, then we found at low speed the trajectory could be correctly recognized. In addition, we tested that if the leds could separately light up corresponding to various trajectory. The resuls are satisfactory, which means we can add more action to the system, such as controlling a motor to water a plant. Thirdly we tested the the legibility of the content displayed by PiTFT, and tested if the physical button is working smoothly (we use physical button to restart the game). At last, we ran the whole system to see if the game is running correctly.
As for the project result, we successfully achieve the functions we had envisioned. The system can implement the recognition of different trajectory of magic wand, and can successfully run the game. When the player move the wand in a slow and stable speed, the spells will be recognized precisely, and the game process shown on Pi TFT are detailed. Overall, the project results are satisfactory, and the deficiencies of the project can be further upgraded and adjusted in the later period.
As a summary, we finally can smoothly run the game with two players. And the camera can successfully recognize the trajectory of wand in most cases. However, the recognition accuracy of the camera is still not enough, which we believe is that the performance of the currently used algorithm cannot meet our expected requirements. After exploration, we find if we capture more feature points in each frame and compare the current frame with the last one. And then calculate the coordinates' movement of similar feature points to obtain the direction of wand. In this method, it can get better results than that of current algorithm. So that in the future, we plan to update the algorithm, use a better camera and some brighter leds. All in all, we achieve the preconceived goals.
hy468@cornell.edu
Designed parts of the software architecture. Tested the whole system. Made parts of website, project report and demonstration video.
hc795@cornell.edu
Designed parts of software architecture. Tested the whole system. Made parts of website, project report and demonstration video.
import io from io import BytesIO import sys import pygame import os from pygame.locals import * from picamera import PiCamera import numpy as np import cv2 import threading import math import time import pigpio import RPi.GPIO as GPIO GPIO.setmode(GPIO.BCM) GPIO.setup(5, GPIO.OUT) GPIO.setmode(GPIO.BCM) GPIO.setup(13, GPIO.OUT) GPIO.setmode(GPIO.BCM) GPIO.setup(19, GPIO.OUT) GPIO.setup(26, GPIO.OUT) GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.setup(27, GPIO.IN, pull_up_down=GPIO.PUD_UP) os.putenv('SDL_VIDEODRIVER', 'fbcon') os.putenv('SDL_FBDEV', '/dev/fb0') os.putenv('SDL_MOUSEDRV', 'TSLIB') os.putenv('SDL_MOUSEDEV', '/dev/input/touchscreen') pygame.init() pygame.mouse.set_visible(False) WHITE = 255, 255, 255 RED = 255, 0, 0 GREEN = 0, 255, 0 BLACK = 0, 0, 0 my_buttons = {'Wizard1':(80,40), 'Wizard2':(240,40), 'Winner':(160,100)} winner = {(160,140):'None'} left_buttons = {(70,90):'None', (70, 130):'None', (70,170):'None', (70,210):'0'} right_buttons = {(250,90):'None', (250, 130):'None', (250,170):'None', (250,210):'0'} my_buttons_rect = {} my_font = pygame.font.Font(None, 35) my_font1 = pygame.font.Font(None, 20) my_font2 = pygame.font.Font(None, 30) screen = pygame.display.set_mode((320,240)) screen.fill(BLACK) flag = True flag1 = False flag2 = False def GPIO5():#the defination of GPIO start = time.time() end = time.time() + 3 while end - start > 0: GPIO.output(5, GPIO.HIGH) time.sleep(0.1) GPIO.output(5, GPIO.LOW) time.sleep(0.1) start = time.time() GPIO.output(5, GPIO.LOW) def GPIO13(): start = time.time() end = time.time() + 3 while end - start > 0: GPIO.output(13, GPIO.HIGH) time.sleep(0.1) GPIO.output(13, GPIO.LOW) time.sleep(0.1) start = time.time() GPIO.output(13, GPIO.LOW) def GPIO19(): start = time.time() end = time.time() + 3 while end - start > 0: GPIO.output(19, GPIO.HIGH) time.sleep(0.1) GPIO.output(19, GPIO.LOW) time.sleep(0.1) start = time.time() GPIO.output(19, GPIO.LOW) def GPIO26(): GPIO.output(26, GPIO.HIGH) def Scan():#call Scan will run find wand global flag while flag: FindNewPoints() def FindNewPoints():#find the circle similar to the front end of wand shown in the video stream global old_frame,old_gray,p0,mask,color,ig,img,frame ig = [[0] for x in range(20)] TrackWand() def TrackWand(): global old_frame,old_gray,p0,mask,color,ig,img,frame,flag,flag1 color = (0,0,255) cam.capture(stream, 'jpeg')#capture a photo file to check the camera view data = np.fromstring(stream.getvalue(), dtype=np.uint8) old_frame = cv2.imdecode(data, 1) cv2.flip(old_frame,1,old_frame) old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY) # find the first frame and find circles in it p0 = cv2.HoughCircles(old_gray,cv2.HOUGH_GRADIENT,3,100,param1=100,param2=30,minRadius=4,maxRadius=15) p0.shape = (p0.shape[1], 1, p0.shape[2]) p0 = p0[:,:,0:2] while flag: my_stream = BytesIO() cam.capture(my_stream, 'jpeg') data2 = np.fromstring(my_stream.getvalue(), dtype=np.uint8) frame = cv2.imdecode(data2, 1) cv2.flip(frame,1,frame) frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)#calculate the video optical flow # Select good points good_new = p1[st==1] good_old = p0[st==1] # draw the tracks for i,(new,old) in enumerate(zip(good_new,good_old)): a,b = new.ravel() # a,c-the x-axis of the center, b,d-the y-axis of the center c,d = old.ravel() if (i<10):# the maximum center of circle can be detected is 10 IsGesture(a,b,c,d,i) if flag1: break flag1 = False#set flags to run the while cam.capture(my_stream, 'jpeg') data3 = np.fromstring(my_stream.getvalue(), dtype=np.uint8) frame = cv2.imdecode(data3, 1) #update the frames and points old_gray = frame_gray.copy()#copy the gray frame p0 = good_new.reshape(-1,1,2) def Spell(spell): global num,flag,lupdate,rupdate ig = [[0] for x in range(8)] if (spell=="Colovaria"): if (num % 2) == 1: lupdate = spell left() elif (num % 2) == 0: rupdate = spell right() num += 1 flag = False print('flag is False') print('Colovaria','leftdown') print(astr) GPIO5() elif (spell=="Lumos"): if (num % 2) == 1: lupdate = spell left() elif (num % 2) == 0: rupdate = spell right() num += 1 flag = False print('flag is False') print('Lumos','rightdown') print(astr) GPIO13() elif (spell=="Nox"): if (num % 2) == 1: lupdate = spell left() elif (num % 2) == 0: rupdate = spell right() num += 1 flag = False print('flag is False') print('Nox','rightup') print(astr) GPIO19() Scan() def IsGesture(a,b,c,d,i): global flag1, astr print("point: %s" % i)# add the movement we recognized into a list if ((a<(c-4)) and (abs(b-d)<2)): ig[i].append("left") elif ((c<(a-4)) and (abs(b-d)<2)): ig[i].append("right") elif (b<(d-6)): ig[i].append("up") elif (d<(b-6)): ig[i].append("down") astr = ''.join(map(str, ig[i])) if "rightdown" in astr: Spell("Lumos") flag1 = True elif "rightup" in astr: Spell("Nox") flag1 = True elif "leftdown" in astr: Spell("Colovaria") flag1 = True print(astr) def left(): left_buttons[(70,90)] = left_buttons[(70,130)] left_buttons[(70,130)] = left_buttons[(70,170)] left_buttons[(70,170)] = lupdate def right(): right_buttons[(250,90)] = right_buttons[(250,130)] right_buttons[(250,130)] = right_buttons[(250,170)] right_buttons[(250,170)] = rupdate def compare(): lvalue = left_buttons[(70,170)] rvalue = right_buttons[(250,170)] if lvalue == 'Nox' and rvalue == 'Lumos': left_buttons[(70,210)] = str(int(left_buttons[(70,210)]) + 1) elif lvalue == 'Lumos' and rvalue == 'Nox': right_buttons[(250,210)] = str(int(right_buttons[(250,210)]) + 1) elif lvalue == 'Lumos' and rvalue == 'Colovaria': left_buttons[(70,210)] = str(int(left_buttons[(70,210)]) + 1) elif lvalue == 'Colovaria' and rvalue == 'Lumos': right_buttons[(250,210)] = str(int(right_buttons[(250,210)]) + 1) elif lvalue == 'Colovaria' and rvalue == 'Nox': left_buttons[(70,210)] = str(int(left_buttons[(70,210)]) + 1) elif lvalue == 'Nox' and rvalue == 'Colovaria': right_buttons[(250,210)] = str(int(right_buttons[(250,210)]) + 1) def getwinner(): lvalue = int(left_buttons[(70,210)]) rvalue = int(right_buttons[(250,210)]) if lvalue == rvalue: winner[(160,140)] = 'doffall' elif lvalue > rvalue: winner[(160,140)] = 'Wizard1' elif lvalue < rvalue: winner[(160,140)] = 'Wizard2' def restart(): global winner, left_buttons, right_buttons, num winner = {(160,140):'None'} left_buttons = {(70,90):'None', (70, 130):'None', (70,170):'None', (70,210):'0'} right_buttons = {(250,90):'None', (250, 130):'None', (250,170):'None', (250,210):'0'} num = 1 lk_params = dict( winSize = (15,15), maxLevel = 2, # cv2.TERM_CRITERIA_COUNT iterate 10 times criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03)) # cv2.TERM_CRITERIA_EPS dilation_params = (5, 5) movment_threshold = 80 num = 1 lupdate = '' rupdate = '' stream = BytesIO() # Create an in-memory stream cam = PiCamera() # initialize cam.framerate = 24 # set frequency def End(): cam.close() while True: flag = True screen.fill(BLACK) if num == 3 or num == 5 or num == 7: compare() if num == 7: num += 1 if num == 8: getwinner() flag2 = True for my_text, text_pos in my_buttons.items(): text_surface = my_font.render(my_text, True, GREEN) rect = text_surface.get_rect(center=text_pos) screen.blit(text_surface, rect) for text_pos, my_text in left_buttons.items(): text_surface = my_font1.render(my_text, True, WHITE) rect = text_surface.get_rect(center=text_pos) screen.blit(text_surface, rect) for text_pos, my_text in right_buttons.items(): text_surface = my_font1.render(my_text, True, WHITE) rect = text_surface.get_rect(center=text_pos) screen.blit(text_surface, rect) for text_pos, my_text in winner.items(): text_surface = my_font1.render(my_text, True, RED) rect = text_surface.get_rect(center=text_pos) screen.blit(text_surface, rect) if (not GPIO.input(27)): sys.exit() pygame.display.flip() if num <= 6: Scan() while flag2: if (not GPIO.input(27)): sys.exit() if (not GPIO.input(23)): restart() flag2 = False